package net.gdface.sdk;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.gdface.image.BaseLazyImage;
import net.gdface.image.ImageErrorException;
import net.gdface.image.MatType;
import net.gdface.image.NotImageException;
import net.gdface.image.UnsupportedFormatException;
import net.gdface.sdk.fse.FeatureSe;
import net.gdface.utils.Assert;

/**
 * {@link FaceApi}抽象实现,SDK接口类必须从此类派生<br>
 * @author guyadong
 *
 */
public abstract class BaseFaceApi implements FaceApi{

	protected BaseFaceApi() {
	}
	@Override
	public double compare2Face(byte[] imgData1, CodeInfo facePos1, byte[] imgData2, CodeInfo facePos2)
			throws NotFaceDetectedException {
		if(0==imgData1.length){
			imgData1=null;
		}
		if(0==imgData2.length){
			imgData2=null;
		}
		byte[] code1, code2;
		Assert.notNull(facePos1, "facePos1");
		Assert.notNull(facePos2, "facePos2");			
		if (imgData1 == imgData2||null==imgData2) {
			CodeInfo[] codes = getCodeInfo(imgData1, 2, new CodeInfo[] { facePos1, facePos2 });			
			code1 = codes[0].getCode();
			code2 = codes[1].getCode();
		} else {
			code1 = getCodeInfo(imgData1, 1, new CodeInfo[] { facePos1 })[0].getCode();
			code2 = getCodeInfo(imgData2, 1, new CodeInfo[] { facePos2 })[0].getCode();
		}
		return compareCode(code1, code2);
	}
	@Override
	public CompareResult compareFaces(byte[] code, byte[] imgData, int faceNum)
			throws NotFaceDetectedException, ImageErrorException {
		CodeInfo[] codes = detectAndGetCodeInfo(imgData,faceNum);
		double[] similartys = compareCodes(code,codes);
		return new CompareResult(codes,similartys);
	}
	@Override
	public double detectAndCompare2Face(byte[] imgData1, FRect detectRect1, byte[] imgData2, FRect detectRect2)
			throws ImageErrorException, NotFaceDetectedException {
		if(null != imgData1 && 0==imgData1.length){
			imgData1=null;
		}
		if(null != imgData2 && 0==imgData2.length){
			imgData2=null;
		}
		byte[] code1, code2;
		if (imgData1 == imgData2||null==imgData2) {
			if (detectRect1 == null || null == detectRect2){
				throw new IllegalArgumentException("detectRect1,detectRect2 must not be null");
			}
			CodeInfo[] codes = detectAndGetCodeInfo(imgData1, 2);
			if((detectRect1.contains(codes[0].getPos()) && detectRect2.contains(codes[1].getPos()))
					|| (detectRect2.contains(codes[0].getPos()) && detectRect1.contains(codes[1].getPos()))){
				code1 = codes[0].getCode();
				code2 = codes[1].getCode();
			}else{
				// 如果任何检测区域一个没有检测到人脸,则抛出异常
				throw new NotFaceDetectedException("not found face in detect rectangle");
			}
		} else {
			code1 = detectAndGetCodeInfo(imgData1, 1)[0].getCode();
			code2 = detectAndGetCodeInfo(imgData2, 1)[0].getCode();
		}
		return compareCode(code1, code2);
	}
	@Override
	public List<Double> compareFeatures(byte[] code1,List<byte[]> codes){
		if(null == code1 || null == codes){
			throw new NullPointerException("code1 or codes is null");
		}
		ArrayList<Double> result = new ArrayList<Double>(codes.size());
		for(byte[] code2:codes){
			result.add(compareCode(code1,code2));
		}
		return result;
	}
	@Override
	public double[] compareCodes(byte[] code1,CodeInfo[] codes){
		if(null == code1 || null == codes){
			throw new NullPointerException("code1 or codes is null");
		}
		double[] result = new double[codes.length];
		for(int i = 0;i<result.length;++i){
			result[i] = compareCode(code1,codes[i].getCode());
		}
		return result;
	}
	/**
	 * 通过图像数据字节流(byte[])创建FaceImage对象<br>
	 * 调用{@link BaseLazyImage#setAutoClose(boolean)}设置 autClose为{@code false}<br>
	 * 如果有特殊要求，子类可以重写此方法
	 * @param imgData
	 * @throws UnsupportedFormatException
	 * @throws NotImageException
	 * @see BaseLazyImage#open()
	 * @see BaseLazyImage#setAutoClose(boolean)
	 */
	protected BaseLazyImage makeOpenedLazyImage(byte[] imgData) throws UnsupportedFormatException, NotImageException {
		return BaseLazyImage.getLazyImageFactory().create(imgData).setAutoClose(false).open();
	}

	@Override
	public CodeInfo detectCenterFace(byte[] imgData) throws NotFaceDetectedException, ImageErrorException {
		BaseLazyImage lazyImg = BaseLazyImage.getLazyImageFactory().create(imgData);
		CodeInfo centerFace;
		CodeInfo[] codes = detectFace(imgData);
		if(codes.length == 0){
			throw new NotFaceDetectedException();
		}
		FInt2 center=new FInt2(lazyImg.getWidth()>>1,lazyImg.getHeight()>>1);
		centerFace=codes[0];
		double minDistance = RectUtils.distance(centerFace.getPos(), center);
		for(int i=1;i<codes.length;++i){
			CodeInfo f=codes[i];
			double d = RectUtils.distance(f.getPos(), center);
			if(d<minDistance){
				centerFace=f;
				minDistance=d;
			}
		}		
		return centerFace;
	}
	protected CodeInfo findMaxFace(CodeInfo[] facePos){
		if(facePos != null && facePos.length != 0){
			int maxArea = 0;
			CodeInfo maxFace = null;
			// 按矩形面积挑出最大人脸矩形
			for(CodeInfo c:facePos){
				if(c != null){
					FRect rect = c.getPos();
					if(rect != null){
						int area =  rect.getWidth()*rect.getHeight();
						if(maxArea < area){
							maxArea = area;
							maxFace = c;
						}
					}
				}
			}
			return maxFace;
		}
		return null;		
	}
	@Override
	public CodeInfo detectMaxFace(byte[] imgData) throws NotFaceDetectedException, ImageErrorException {
		CodeInfo[] codes = detectFace(imgData);
		CodeInfo maxFace = findMaxFace(codes);
		if(maxFace == null){
			throw new NotFaceDetectedException();
		}
		return maxFace;
	}
	
	@Override
	public CodeInfo detectMaxFaceAndGetCodeInfo(byte[] imgData) throws ImageErrorException, NotFaceDetectedException{
		CodeInfo codeInfo = detectMaxFace(imgData);
		codeInfo = getCodeInfo(imgData, codeInfo);
		if (null == codeInfo){
			throw new NotFaceDetectedException();
		}
		return codeInfo;
	}
	
	@Override
	public boolean hasFace(byte[] imgData) throws ImageErrorException {
		try {
			detectAndGetCodeInfo(imgData, 0);
			return true;
		} catch (NotFaceDetectedException e) {
			return false;
		}
	}
	@Override
	public boolean matHasFace(MatType matType,byte[] matData,int width, int height){
		try {
			matDetectAndGetCodeInfo(matType, matData, width,height, 0);
			return true;
		} catch (NotFaceDetectedException e) {
			return false;
		}
	}
	@Override
	public CodeInfo matDetectMaxFace(MatType matType,byte[] matData,int width, int height) throws NotFaceDetectedException {
		CodeInfo[] facePos = matDetectFace(matType, matData, width,height);
		CodeInfo maxFace = findMaxFace(facePos);
		if(maxFace == null){
			throw new NotFaceDetectedException();
		}
		return maxFace;
	}
	
	@Override
	public CodeInfo matDetectMaxFaceAndGetCodeInfo(MatType matType, byte[] matData, int width, int height) throws NotFaceDetectedException{
		CodeInfo codeInfo = matDetectMaxFace(matType, matData, width, height);
		codeInfo = matGetCodeInfo(matType,matData, width,height,codeInfo);
		if(null == codeInfo){
			throw new NotFaceDetectedException();
		}
		return codeInfo;
	}
	@Override
	public byte[] getFeature(Map<ByteBuffer, CodeInfo> faces) throws NotFaceDetectedException {
		throw new UnsupportedOperationException();
	}
	@Override
	public FseResult[] searchFaces(byte[] imgData, CodeInfo facePos, double similarty, int rows, String[] imgMD5Set, int group)
			throws NotFaceDetectedException, ImageErrorException {
		if(null == facePos ){
			facePos = detectAndGetCodeInfo(imgData, 1)[0];
		}else if(null == facePos.getCode()){
			facePos = getCodeInfo(imgData, 1, new CodeInfo[] {facePos})[0];
		}
		return this.searchFeatures(facePos.getCode(), similarty, rows, imgMD5Set, group);
	}
	@Override
	public FseResult[] matSearchFaces(MatType matType, byte[] matData, int width, int height, CodeInfo facePos, double similarty,
			int rows, String[] imgMD5Set, int group) throws NotFaceDetectedException, ImageErrorException {
		if(null == facePos ){
			facePos = matDetectAndGetCodeInfo(matType,matData,width,height, 1)[0];
		}else if(null == facePos.getCode()){
			facePos = matGetCodeInfo(matType,matData,width,height, facePos);
		}
		return this.searchFeatures(facePos.getCode(), similarty, rows, imgMD5Set, group);
	}
	/**
	 * 返回用于图像检测和建模的图像矩阵的(色彩空间)类型<br>
	 * 不可返回{@code null}
	 * @return 矩阵类型
	 */
	protected abstract MatType getNativeMatrixType();

	/**
	 * 从{@link FaceApi}实例获取内存搜索引擎接口实例<br>
	 * 如果{@link FaceApi}不是{@link BaseFaceApiLocal}实例返回{@code null}
	 * @param faceapi
	 */
	public static final FeatureSe getFeatureSeInstance(FaceApi faceapi){
		if(faceapi instanceof BaseFaceApiLocal){			
			return ((BaseFaceApiLocal)faceapi).getFeatureSe();
		}
		return null;
	}
}